Flutter异步编程
重点
- Dart代码运行在一个已执行的线程内。
- 阻塞线程执行的代码能够使程序冻结。
- Future对象(Futures)表示异步执行的结果(将要完成的执行结果或I/O操作)。
- 异步函数使用await()(或者用then()),暂停执行直到future完成。
- 异步函数使用try-cache表达式,捕获错误。
- 构造一个isolate(web 使用 worker),立刻运行代码,
Dart代码运行在一个已执行的线程内。如果Dart代码阻塞,例如,运行一个长时间运算或者等待I/O操作,程序将要冻结。
异步操作可以让你在等待一个操作完成时,进行其他工作。Dart使用Future对象(Futures)表示异步执行结果。
你可以使用async和await也可以使用Future API,进行futures开发。
Node: 所有Dart代码运行在isolate上下文中,这个isolate拥有Dart代码所有内存。当Dart代码执行时,isolate内不能运行其他代码。
如果你想多部分Dart代码同时运行,你能运行他们在其他的isolate(Web 用workers代替 isolate)。多个isolate同时运行,通常运行在各自的CPU内核上。isolate不共享内存,他们之间的唯一沟通方式是发送消息。关于isolate更多内容,请查看文档isolates或者web workers
介绍
让我们看一些可能导致程序冻结的代码:
1 | // Synchronous code |
我们的程序收集当天的新闻,并打印它,然后打印一些用户感兴趣的其他项目:
1 | <gathered news goes here> |
我们的代码是有潜在问题的:由于gatherNewsReports()阻塞,余下的代码只能一直等待gatherNewsReports()从文件读取返回值之后执行。如果读取文件花费很长时间,用户必须等,尽管用户可能更想知道他们是否赢了彩票,明天的天气是什么,谁赢了今天的比赛。
为了保证程序响应,Dart库作者处理耗时操作时使用异步模式,这些函数使用future作为返回值。
#Future 是什么?
future是一个Future
- 该函数将加入工作队列,并返回一个未完成的Future对象。
- 之后,当操作完成后,返回完成的Future对象值或者错误。
使用future,有两种方式:
- 用async 和 await
- 用Future API
Async 和 await
async和await是Dart支持异步编程的一部分。他们允许你写异步代码,看起来像同步代码并且不需要使用Future API。
异步函数即是将async关键字放在函数体前即可。await关键字只用在async函数。
下面程序使用async和await模拟从 www.dartlang.org. 读取内容:
1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
运行结果:
1 | Winning lotto numbers: [23, 63, 87, 26, 2] |
printDailyNewsDigest()是第一个被调用的,虽然只是输出一行,但是新闻也是最后被打印的。这是因为运行和打印代码是异步运行的。
在这个例子中,printDailyNewsDigest()调用非阻塞gatherNewsReports(),调用gatherNewsReports()会将执行任务加入队列,但不会阻止剩下代码执行。程序打印彩票号码,预测棒球比赛分数。当gatherNewsReports() 完成获得新闻后,打印它。如果gatherNewsReports()花费一些时间完成,由于异步执行也不会给用户带来很大的影响。在打印每日新闻之前用户可以阅读其他消息。
请留意返回类型, gatherNewsReports()的返回类型是Future
下图展示调用流程,数字和步骤相互对应:
- 程序开始运行
- main函数调用printDailyNewsDigest(),printDailyNewsDigest()开始执行。
- printDailyNewsDigest()使用await调用atherNewsReports(),atherNewsReports()开始执行。
- gatherNewsReports()返回一个未完成的future(一个Future
实例)。 - 因为printDailyNewsDigest()是一个异步函数并且等待返回值,它暂停执行并返回一个未完成的future(这种情况下,返回的是Future
)给它的调用者(这里是main函数)。 - 执行剩下的打印函数。因为他们是同步的,每个函数完全执行完,才会执行下一个函数。例如,彩票中奖号码都是在天气预报之前打印出来的。
- 当main()函数完成执行,异步函数仍然能执行。首先gatherNewsReports() 完成后返回future,然后 printDailyNewsDigest()继续执行,打印新闻。
- 当printDailyNewsDigest()函数体完成执行,完成的future返回,程序退出。
注意,异步函数立即(同步地)开始执行。当第一次出现一下情况时,函数暂停执行,并返回一个未完成的future:
- 函数的第一个await表达式(函数获得未完成future之后)。
- reture 语句。
- 函数体结束。
错误处理
异步函数使用try-cache进行错误处理。
1 | Future<void> printDailyNewsDigest() async { |
try-cache的行为在异步代码和同步代码中是相同的,如果try块中的代码抛出异常,则catch子句中的代码将执行。
顺序处理
您可以使用多个await表达式来确保顺序执行,即每个语句在执行下一个语句之前完成:
1 | // Sequential processing using async and await. |
expensiveA()执行完成后,才会执行expensiveB().
#Future API
在Dart 1.9中添加async和await之前,您必須使用Future API。目前仍然可能在老的Dart代码中以及需要比asyn-await提供更丰富功能的代码中,看到Future API。
使用Future API写异步代码,用then()注册回调。当Future完成后,回调被执行。
下面程序使用Future API模拟从 www.dartlang.org. 读取内容:
1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
结果输出:
1 | Winning lotto numbers: [23, 63, 87, 26, 2] |
printDailyNewsDigest()是第一个被调用的,虽然只是输出一行,但是新闻也是最后被打印的。这是因为运行和打印代码是异步运行的。
程序执行步骤:
- 开始执行
- main()函数调用printDailyNewsDigest(),printDailyNewsDigest()没有立即返回,而是调用了gatherNewsReports().
- gatherNewsReports()开始获取新闻并且返回一个Future。
- printDailyNewsDigest()使用then()处理对应的Future返回值。调用then()返回一个新的Future,它将作为then()的回调参数。
- 执行剩下的打印函数。因为他们是同步的,每个函数完全执行完,才会执行下一个函数。例如,彩票中奖号码都是在天气预报之前打印出来的。
- 当所有的新闻都收到后, gatherNewsReports()完成后返回包含新闻信息字符串的Future。
- 在printDailyNewsDigest()中指定的then()执行,打印新闻。
- 退出程序。
Node : 在printDailyNewsDigest()函数,future.then(print) 等价于:
future.then((newsDigest) => print(newsDigest))
另外,then()内部代码可以使用{}:
1 | Future<void> printDailyNewsDigest() { |
你需要提供一个参数给then()的回调,即使Future是Future
1 | final future = printDailyNewsDigest(); |
##错误处理
使用Future API,你能用catchError()捕获错误。
1 | Future<void> printDailyNewsDigest() => |
如果新闻数据读取无效,代码的执行流程如下:
- gatherNewsReports()返回包含错误信息的future。
- then()返回的future以错误结束,print()函数不被掉用。
- catchError() (handleError())处理错误, catchError()返回的正常的future,并且错误不会被传播。
链式模式是Future API的常见模式。可以将Future API 等同于try-catch模块。
与then()类似,catchError()返回一个新的Future,它是回调的返回值。
更多详细信息和例子,请参考Futures and Error Handling.
调用多个函数
考虑三个函数,expensiveA(), expensiveB(), 和expensiveC(),这三个函数都返回Future对象。你能顺序调用他们,或者你能同时开始他们,并在所有函数执行完成后做一些事情。Future接口可以轻松的处理这两种用例。
使用then()进行链式调用
当函数需要按顺序执行时,使用链式then():
1 | expensiveA() |
嵌套回调虽然可以工作,但是难于阅读。
使用Future.wait()等待多个future完成
如果函数的执行顺序不重要,你可以用Future.wait()。
当你传递一个future列表给Future.wait(),它立刻返回一个未完成的future。直到给定的future列表全部执行完这个future才完成。future的返回值,由列表中的每个future的返回值组成。
1 | Future.wait([expensiveA(), expensiveB(), expensiveC()]) |
如果任何一个函数返回错误,Future.wait()的futrue都以错误结束。使用catchError()处理错误。
#其他资源
阅读以下文档,了解有关在Dart中使用future和异步编程的更多详细信息: